001 /* 002 * Copyright 2006 Stephen J. McConnell. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.lang; 020 021 import java.io.Writer; 022 import java.io.IOException; 023 import java.lang.reflect.Constructor; 024 import java.lang.reflect.InvocationTargetException; 025 import java.lang.reflect.Array; 026 import java.beans.Expression; 027 import java.util.Arrays; 028 import java.util.ArrayList; 029 import java.util.Iterator; 030 031 import net.dpml.util.Logger; 032 033 /** 034 * Plugin part strategy implementation datatype. 035 * @author <a href="http://www.dpml.net">The Digital Product Meta Library</a> 036 * @version 1.0.0 037 */ 038 public class Plugin extends Part 039 { 040 private final String m_classname; 041 private final Value[] m_params; 042 043 /** 044 * Creation of an new plugin datatype. 045 * @param logger the assigned logging channel 046 * @param info the part info descriptor 047 * @param classpath the classpath descriptor 048 * @param classname the target class 049 * @exception IOException if an I/O error occurs 050 */ 051 public Plugin( 052 Logger logger, Info info, Classpath classpath, String classname ) 053 throws IOException 054 { 055 this( logger, info, classpath, classname, new Value[0] ); 056 } 057 058 /** 059 * Creation of an new plugin datatype. 060 * @param logger the assigned logging channel 061 * @param info the part info descriptor 062 * @param classpath the classpath descriptor 063 * @param classname the target class 064 * @param params an array of default value arguments 065 * @exception IOException if an I/O error occurs 066 */ 067 public Plugin( 068 Logger logger, Info info, Classpath classpath, String classname, Value[] params ) 069 throws IOException 070 { 071 super( logger, info, classpath ); 072 if( null == classname ) 073 { 074 throw new NullPointerException( "classname" ); 075 } 076 if( null == params ) 077 { 078 throw new NullPointerException( "params" ); 079 } 080 m_classname = classname; 081 m_params = params; 082 } 083 084 /** 085 * Return the part content or null if the result type is unresolvable 086 * relative to the supplied class argument. 087 * @param c the content class 088 * @return the content 089 * @exception IOException if an IO error occurs 090 */ 091 protected Object getContent( Class c ) throws IOException 092 { 093 if( Class.class.equals( c ) ) 094 { 095 return getPluginClass(); 096 } 097 else 098 { 099 return super.getContent( c ); 100 } 101 } 102 103 /** 104 * Get the target classname. 105 * @return the classname 106 */ 107 public String getClassname() 108 { 109 return m_classname; 110 } 111 112 /** 113 * Get the array of default constructor values. 114 * @return the value array 115 */ 116 public Value[] getValues() 117 { 118 return m_params; 119 } 120 121 /** 122 * Get the default plugin class. 123 * @return the plugin class 124 */ 125 public Class getPluginClass() 126 { 127 ClassLoader classloader = getClassLoader(); 128 String classname = getClassname(); 129 try 130 { 131 return classloader.loadClass( classname ); 132 } 133 catch( ClassNotFoundException e ) 134 { 135 final String error = 136 "Plugin class [" + classname + "] not found."; 137 throw new IllegalStateException( error ); 138 } 139 } 140 141 /** 142 * Instantiate a value. 143 * @param args supplimentary arguments 144 * @return the resolved instance 145 * @exception Exception if a deployment error occurs 146 */ 147 public Object instantiate( Object[] args ) throws Exception 148 { 149 ClassLoader classloader = getClassLoader(); 150 ClassLoader context = Thread.currentThread().getContextClassLoader(); 151 Thread.currentThread().setContextClassLoader( classloader ); 152 try 153 { 154 Value[] values = getValues(); 155 Class c = getPluginClass(); 156 Object[] params = Construct.getArgs( null, values, args ); 157 return instantiate( c, params ); 158 } 159 finally 160 { 161 Thread.currentThread().setContextClassLoader( context ); 162 } 163 } 164 165 /** 166 * Test if this instance is equal to the supllied object. 167 * @param other the other object 168 * @return true if the supplied object is equal to this instance 169 */ 170 public boolean equals( Object other ) 171 { 172 if( super.equals( other ) ) 173 { 174 if( other instanceof Plugin ) 175 { 176 Plugin plugin = (Plugin) other; 177 if( !m_classname.equals( plugin.m_classname ) ) 178 { 179 return false; 180 } 181 else 182 { 183 return Arrays.equals( m_params, plugin.m_params ); 184 } 185 } 186 else 187 { 188 return false; 189 } 190 } 191 else 192 { 193 return false; 194 } 195 } 196 197 /** 198 * Get the has code for this instance. 199 * @return the hash value 200 */ 201 public int hashCode() 202 { 203 int hash = m_classname.hashCode(); 204 for( int i=0; i<m_params.length; i++ ) 205 { 206 hash ^= m_params[i].hashCode(); 207 } 208 return hash; 209 } 210 211 212 /** 213 * Encode the pluginstrategy to XML. 214 * @param writer the output stream writer 215 * @param pad the character offset 216 * @exception IOException if an I/O error occurs 217 */ 218 protected void encodeStrategy( Writer writer, String pad ) throws IOException 219 { 220 String classname = getClassname(); 221 writer.write( "\n" + pad + "<strategy xsi:type=\"plugin\" class=\"" ); 222 writer.write( classname ); 223 writer.write( "\"" ); 224 if( getValues().length > 0 ) 225 { 226 writer.write( ">" ); 227 Value[] values = getValues(); 228 VALUE_ENCODER.encodeValues( writer, values, pad + " " ); 229 writer.write( "\n" + pad + "</strategy>" ); 230 } 231 else 232 { 233 writer.write( "/>" ); 234 } 235 } 236 237 /** 238 * Create a factory using a supplied class and command line arguments. 239 * 240 * @param clazz the the factory class 241 * @param args the command line args 242 * @return the plugin instance 243 * @exception IOException if a plugin creation error occurs 244 * @exception InvocationTargetException if a plugin constructor invocation error occurs 245 */ 246 public static Object instantiate( Class clazz, Object[] args ) throws IOException, InvocationTargetException 247 { 248 if( null == clazz ) 249 { 250 throw new NullPointerException( "clazz" ); 251 } 252 if( null == args ) 253 { 254 throw new NullPointerException( "args" ); 255 } 256 for( int i=0; i < args.length; i++ ) 257 { 258 Object p = args[i]; 259 if( null == p ) 260 { 261 final String error = 262 "User supplied instantiation argument at position [" 263 + i 264 + "] for the class [" 265 + clazz.getName() 266 + "] is a null value."; 267 throw new NullPointerException( error ); 268 } 269 } 270 271 if( clazz.getConstructors().length == 1 ) 272 { 273 Constructor constructor = getSingleConstructor( clazz ); 274 return instantiate( constructor, args ); 275 } 276 else 277 { 278 try 279 { 280 Expression expression = new Expression( clazz, "new", args ); 281 return expression.getValue(); 282 } 283 catch( InvocationTargetException e ) 284 { 285 throw e; 286 } 287 catch( PartException e ) 288 { 289 throw e; 290 } 291 catch( Throwable e ) 292 { 293 final String error = 294 "Class instantiation error [" + clazz.getName() + "]"; 295 throw new PartException( error, e ); 296 } 297 } 298 } 299 300 private static Object instantiate( Constructor constructor, Object[] args ) 301 throws PartException, InvocationTargetException 302 { 303 Object[] arguments = populate( constructor, args ); 304 return newInstance( constructor, arguments ); 305 } 306 307 private static Object[] populate( Constructor constructor, Object[] args ) throws PartException 308 { 309 if( null == constructor ) 310 { 311 throw new NullPointerException( "constructor" ); 312 } 313 if( null == args ) 314 { 315 throw new NullPointerException( "args" ); 316 } 317 318 Class[] classes = constructor.getParameterTypes(); 319 Object[] arguments = new Object[ classes.length ]; 320 ArrayList list = new ArrayList(); 321 for( int i=0; i < args.length; i++ ) 322 { 323 list.add( args[i] ); 324 } 325 326 // 327 // sweep though the construct arguments one by one and 328 // see if we can assign a value based on the supplied args 329 // 330 331 for( int i=0; i < classes.length; i++ ) 332 { 333 Class clazz = classes[i]; 334 Iterator iterator = list.iterator(); 335 while( iterator.hasNext() ) 336 { 337 Object object = iterator.next(); 338 Class c = object.getClass(); 339 if( isAssignableFrom( clazz, c ) ) 340 { 341 arguments[i] = object; 342 list.remove( object ); 343 break; 344 } 345 } 346 } 347 348 // 349 // if any arguments are unresolved then check if the argument type 350 // is something we can implicity establish 351 // 352 353 for( int i=0; i < arguments.length; i++ ) 354 { 355 if( null == arguments[i] ) 356 { 357 Class c = classes[i]; 358 if( c.isArray() ) 359 { 360 arguments[i] = getEmptyArrayInstance( c ); 361 } 362 else 363 { 364 final String error = 365 "Unable to resolve a value for a constructor parameter." 366 + "\nConstructor class: " + constructor.getDeclaringClass().getName() 367 + "\nParameter class: " + c.getName() 368 + "\nParameter position: " + ( i + 1 ); 369 throw new PartException( error ); 370 } 371 } 372 } 373 return arguments; 374 } 375 376 private static boolean isAssignableFrom( Class clazz, Class c ) 377 { 378 if( clazz.isPrimitive() ) 379 { 380 if( Integer.TYPE == clazz ) 381 { 382 return Integer.class.isAssignableFrom( c ); 383 } 384 else if( Boolean.TYPE == clazz ) 385 { 386 return Boolean.class.isAssignableFrom( c ); 387 } 388 else if( Byte.TYPE == clazz ) 389 { 390 return Byte.class.isAssignableFrom( c ); 391 } 392 else if( Short.TYPE == clazz ) 393 { 394 return Short.class.isAssignableFrom( c ); 395 } 396 else if( Long.TYPE == clazz ) 397 { 398 return Long.class.isAssignableFrom( c ); 399 } 400 else if( Float.TYPE == clazz ) 401 { 402 return Float.class.isAssignableFrom( c ); 403 } 404 else if( Double.TYPE == clazz ) 405 { 406 return Double.class.isAssignableFrom( c ); 407 } 408 else 409 { 410 final String error = 411 "Primitive type [" 412 + c.getName() 413 + "] not supported."; 414 throw new RuntimeException( error ); 415 } 416 } 417 else 418 { 419 return clazz.isAssignableFrom( c ); 420 } 421 } 422 423 private static Object newInstance( Constructor constructor, Object[] arguments ) 424 throws PartException, InvocationTargetException 425 { 426 try 427 { 428 Object instance = constructor.newInstance( arguments ); 429 //getMonitor().pluginInstantiated( instance ); 430 return instance; 431 } 432 catch( InvocationTargetException e ) 433 { 434 throw e; 435 } 436 catch( Throwable e ) 437 { 438 final String error = 439 "Cannot create an instance of [" 440 + constructor.getDeclaringClass().getName() 441 + "] due to an instantiation failure."; 442 throw new PartException( error, e ); 443 } 444 } 445 446 private static Constructor getSingleConstructor( Class clazz ) throws PartException 447 { 448 if( null == clazz ) 449 { 450 throw new NullPointerException( "clazz" ); 451 } 452 Constructor[] constructors = clazz.getConstructors(); 453 if( constructors.length < 1 ) 454 { 455 final String error = 456 "Target class [" 457 + clazz.getName() 458 + "] does not declare a public constructor."; 459 throw new PartException( error ); 460 } 461 else if( constructors.length > 1 ) 462 { 463 final String error = 464 "Target class [" 465 + clazz.getName() 466 + "] declares multiple public constructors."; 467 throw new PartException( error ); 468 } 469 else 470 { 471 return constructors[0]; 472 } 473 } 474 475 /** 476 * Constructs an empty array instance. 477 * @param clazz the array class 478 * @return the empty array instance 479 * @exception RepositoryException if an error occurs 480 */ 481 private static Object[] getEmptyArrayInstance( Class clazz ) throws PartException 482 { 483 try 484 { 485 return (Object[]) Array.newInstance( clazz.getComponentType(), 0 ); 486 } 487 catch( Throwable e ) 488 { 489 final String error = 490 "Internal error while attempting to construct an empty array for the class: " 491 + clazz.getName(); 492 throw new PartException( error, e ); 493 } 494 } 495 }